namespace Viyi.Util.DateTimes;

/// <summary>
/// 日期和时间扩展方法工具类
/// </summary>
/// <example>
/// <code>
/// string s1 = DateTime.Now.Format();
/// string s2 = DateTime.Now.FormatTime();
/// string s3 = DateTime.Today.FormatDate();
/// string s4 = DateTime.Now.FormatRfc3339();
/// string s5 = DateTime.Now.FormatRfc3339(DateTimeKind.Utc);
/// </code>
/// </example>
public static class DateTimeExtensions {
    private static IFormats Formats => DateTimeHelper.Formats;
    private static IFormats DefaultFormats => DateTimeHelper.DefaultFormats;

    /// <summary>
    /// 计算从1970年1月1日到指定日期的毫秒数数，与 Java 中 <c>java.util.Date.getTime()</c> 返回的值相同，即 UnixTime。
    /// </summary>
    /// <param name="value"></param>
    /// <param name="supplementTimeZone"></param>
    /// <param name="forceTimeZone"></param>
    /// <returns></returns>
    public static long ToJavaMilliseconds(
        this DateTime value,
        TimeZoneInfo? supplementTimeZone = null,
        bool forceTimeZone = false) {
        return ToUnixTimeMilliseconds(value, supplementTimeZone, forceTimeZone);
    }

    /// <summary>
    /// Returns the number of milliseconds that have elapsed since 1970-01-01T00:00:00Z
    /// </summary>
    public static long ToUnixTimeMilliseconds(
        this DateTime value,
        TimeZoneInfo? supplementTimeZone = null,
        bool forceTimeZone = false) {
        return value
            .ToDateTimeOffset(supplementTimeZone, forceTimeZone)
            .ToUniversalTime()
            .ToUnixTimeMilliseconds();
    }

    /// <summary>
    /// Returns the number of seconds that have elapsed since 1970-01-01T00:00:00Z
    /// </summary>
    public static long ToUnixTimeSeconds(
        this DateTime value,
        TimeZoneInfo? supplementTimeZone = null,
        bool forceTimeZone = false) {
        return value
            .ToDateTimeOffset(supplementTimeZone, forceTimeZone)
            .ToUniversalTime()
            .ToUnixTimeSeconds();
    }

    /// <summary>
    /// 把 DateTime 转换成 DateTimeOffset
    /// </summary>
    /// <param name="value">需要转换的 DateTime</param>
    /// <param name="supplementTimeZone">如果 value.Kind 是 Unspecified，使用这个参数提供 time zone</param>
    /// <param name="forceTimeZone">如果提供了 supplementTimeZone，可以通过这个参数强制使用它（不关心 value.Kind）</param>
    /// <returns></returns>
    /// <exception cref="ArgumentNullException">指定 forceTimeZone 为 `true`，但没提供 `supplementTimeZone` 时</exception>
    public static DateTimeOffset ToDateTimeOffset(
        this DateTime value,
        TimeZoneInfo? supplementTimeZone = null,
        bool forceTimeZone = false
    ) {
        if (forceTimeZone && supplementTimeZone == null) {
            throw new ArgumentNullException(nameof(supplementTimeZone));
        }

        var timeZone = forceTimeZone ? supplementTimeZone!
            : value.Kind == DateTimeKind.Utc ? TimeZoneInfo.Utc : TimeZoneInfo.Local;

        return new DateTimeOffset(value, timeZone.GetUtcOffset(DateTimeOffset.UtcNow));
    }

    /// <summary>
    /// 根据全局日期时间格式，格式指定日期对象得到格式化后的字符串
    /// </summary>
    /// <param name="datetime"></param>
    /// <returns></returns>
    public static string Format(this DateTime datetime) {
        return datetime.ToString(Formats.DateTime ?? DefaultFormats.DateTime);
    }

    /// <summary>
    /// 根据全局日期格式，格式化指定日期对象得到格式化后的字符串
    /// </summary>
    /// <param name="date"></param>
    /// <returns></returns>
    public static string FormatDate(this DateTime date) {
        return date.ToString(Formats.Date ?? DefaultFormats.Date);
    }

    /// <summary>
    /// 根据全局时间格式，格式化指定时间对象得到格式化后的字符串
    /// </summary>
    /// <param name="time"></param>
    /// <returns></returns>
    public static string FormatTime(this DateTime time) {
        return time.ToString(Formats.Time ?? DefaultFormats.Time);
    }
    /// <summary>
    /// <p>根据 RFC 3339 规定，得到表示日期的字符串。</p>
    /// <p>结果示例</p>
    /// <ul>
    ///     <li>2014-10-20T01:23:45Z</li>
    ///     <li>2014-10-20T09:23:45+08:00</li>
    /// </ul>
    /// </summary>
    /// <param name="value">日期时间值</param>
    /// <param name="defaultKind">当 <paramref name="value"/>
    /// 的 <c>Kind</c> 属性值是 <see cref="DateTimeKind.Unspecified" /> 时，
    /// 用 <paramref name="defaultKind" /> 代替。
    /// </param>
    /// <returns></returns>
    /// <remark>
    /// 如果 <c>value.Kind == DateTimeKind.Unspecified</c>，默认格式化结果不符合 RFC3339 标准，
    /// 没有后缀的 <c>Z</c> 或时区偏移量。而通常从数据库获取的日期都是没有指定 <c>Kind</c> 的。
    /// 这种情况下，使用 <c>DateTimeKind.Local</c> 作为 <c>DateTimeKind</c>。
    /// </remark>
    public static string FormatRfc3339(
        this DateTime value,
        DateTimeKind defaultKind = DateTimeKind.Unspecified
    ) {
        if (value.Kind == DateTimeKind.Unspecified && value.Kind != defaultKind) {
            value = new DateTime(value.Ticks, defaultKind);
        }

        var format = value.Kind switch {
            DateTimeKind.Utc => "yyyy-MM-dd'T'HH:mm:ssZ",
            _ => "yyyy-MM-dd'T'HH:mm:ssK",
        };

        return value.ToString(format);
    }
}
